package com.rickpan.storage.impl;

import com.rickpan.config.MinIOProperties;
import com.rickpan.exception.BusinessException;
import com.rickpan.storage.StorageService;
import com.rickpan.storage.multipart.MinIOMultipartUploadManager;
import com.rickpan.storage.url.MinIOUrlGenerationService;
import com.rickpan.storage.monitor.StorageMetricsService;
// import com.rickpan.storage.exception.MinIOExceptionHandler;
// import com.rickpan.storage.monitor.MinIOMetrics;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * MinIO 存储服务实现
 * 提供基于 MinIO 对象存储的文件管理功能
 */
@Service
@ConditionalOnProperty(name = "app.storage.type", havingValue = "minio")
public class MinIOStorageServiceImpl implements StorageService {
    
    private static final Logger logger = LoggerFactory.getLogger(MinIOStorageServiceImpl.class);
    
    @Autowired
    private MinioClient minioClient;
    
    @Autowired
    private MinIOProperties minIOProperties;

    @Autowired
    private MinIOMultipartUploadManager multipartUploadManager;

    @Autowired
    private MinIOUrlGenerationService urlGenerationService;

    @Autowired
    private StorageMetricsService metricsService;

    // @Autowired(required = false)
    // private MinIOExceptionHandler exceptionHandler;

    // @Autowired(required = false)
    // private MinIOMetrics metrics;

    // 分片上传临时前缀
    private static final String CHUNK_PREFIX = "chunks/";
    
    @Override
    public String storeFile(MultipartFile file) throws IOException {
        logger.debug("MinIO存储文件: {}, 大小: {}", file.getOriginalFilename(), file.getSize());

        io.micrometer.core.instrument.Timer.Sample sample = metricsService.startOperation("upload", "minio");
        boolean success = false;

        try {
            // 确保存储桶存在
            ensureBucketExists();

            // 生成对象键
            String objectKey = generateObjectKey(file.getOriginalFilename());

            // 上传文件到 MinIO
            try (InputStream inputStream = file.getInputStream()) {
                minioClient.putObject(
                    PutObjectArgs.builder()
                        .bucket(minIOProperties.getBucketName())
                        .object(objectKey)
                        .stream(inputStream, file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build()
                );
            }

            success = true;
            logger.info("MinIO文件存储成功: {} -> {}", file.getOriginalFilename(), objectKey);
            return objectKey;

        } catch (Exception e) {
            logger.error("MinIO文件存储失败: {}", file.getOriginalFilename(), e);

            // 记录错误
            metricsService.recordError("upload", "minio", e.getClass().getSimpleName());

            throw new IOException("MinIO文件存储失败: " + e.getMessage(), e);

        } finally {
            // 记录操作完成
            metricsService.recordOperationComplete(sample, "upload", "minio", success, file.getSize());
        }
    }
    
    @Override
    public String initializeChunkUpload(String uploadId, String originalFileName) throws IOException {
        logger.debug("MinIO初始化分片上传: uploadId={}, fileName={}", uploadId, originalFileName);

        try {
            // 确保存储桶存在
            ensureBucketExists();

            // 生成对象键
            String objectKey = generateObjectKey(originalFileName);

            // 初始化多部分上传
            MinIOMultipartUploadManager.MultipartUploadSession session =
                multipartUploadManager.initializeMultipartUpload(uploadId, objectKey);

            logger.info("MinIO分片上传初始化成功: uploadId={}, objectKey={}", uploadId, objectKey);
            return objectKey;

        } catch (Exception e) {
            logger.error("MinIO分片上传初始化失败: uploadId={}", uploadId, e);
            throw new IOException("MinIO分片上传初始化失败: " + e.getMessage(), e);
        }
    }

    @Override
    public String storeChunk(String uploadId, int chunkIndex, MultipartFile chunk) throws IOException {
        logger.debug("MinIO存储分片: uploadId={}, chunkIndex={}, 大小={}",
                    uploadId, chunkIndex, chunk.getSize());

        long startTime = System.currentTimeMillis();
        boolean success = false;

        try {
            // 使用多部分上传管理器上传分片
            String etag = multipartUploadManager.uploadPart(uploadId, chunkIndex, chunk);

            success = true;
            logger.debug("MinIO分片存储成功: uploadId={}, chunkIndex={}, etag={}",
                        uploadId, chunkIndex, etag);
            return etag;

        } catch (Exception e) {
            logger.error("MinIO分片存储失败: uploadId={}, chunkIndex={}", uploadId, chunkIndex, e);

            // 记录错误
            metricsService.recordError("chunk_upload", "minio", e.getClass().getSimpleName());

            throw new IOException("MinIO分片存储失败: " + e.getMessage(), e);

        } finally {
            // 记录分片上传操作
            Duration duration = Duration.ofMillis(System.currentTimeMillis() - startTime);
            metricsService.recordChunkUpload(duration, success, chunk.getSize(), chunkIndex, "minio");
        }
    }
    
    @Override
    public String mergeChunks(String uploadId, String originalFileName, int totalChunks) throws IOException {
        logger.debug("MinIO合并分片: uploadId={}, fileName={}, totalChunks={}",
                    uploadId, originalFileName, totalChunks);

        try {
            // 获取上传会话信息
            MinIOMultipartUploadManager.MultipartUploadSession session =
                multipartUploadManager.getUploadSession(uploadId);

            if (session == null) {
                throw new IllegalStateException("多部分上传会话不存在: " + uploadId);
            }

            // 完成多部分上传
            String etag = multipartUploadManager.completeMultipartUpload(uploadId);

            logger.info("MinIO分片合并成功: uploadId={}, objectKey={}, etag={}",
                       uploadId, session.getObjectKey(), etag);

            return session.getObjectKey();

        } catch (Exception e) {
            logger.error("MinIO分片合并失败: uploadId={}", uploadId, e);

            // 尝试中止多部分上传
            try {
                multipartUploadManager.abortMultipartUpload(uploadId);
                logger.info("已中止失败的多部分上传: {}", uploadId);
            } catch (Exception abortException) {
                logger.warn("中止多部分上传失败: {}", uploadId, abortException);
            }

            throw new IOException("MinIO分片合并失败: " + e.getMessage(), e);
        }
    }
    
    @Override
    public boolean deleteFile(String objectKey) {
        logger.debug("MinIO删除文件: {}", objectKey);
        
        try {
            minioClient.removeObject(
                RemoveObjectArgs.builder()
                    .bucket(minIOProperties.getBucketName())
                    .object(objectKey)
                    .build()
            );
            
            logger.info("MinIO文件删除成功: {}", objectKey);
            return true;
            
        } catch (Exception e) {
            logger.error("MinIO文件删除失败: {}", objectKey, e);
            return false;
        }
    }
    
    @Override
    public boolean fileExists(String objectKey) {
        try {
            minioClient.statObject(
                StatObjectArgs.builder()
                    .bucket(minIOProperties.getBucketName())
                    .object(objectKey)
                    .build()
            );
            return true;
            
        } catch (ErrorResponseException e) {
            if ("NoSuchKey".equals(e.errorResponse().code())) {
                return false;
            }
            logger.warn("MinIO检查文件存在性时出错: {}", objectKey, e);
            return false;
            
        } catch (Exception e) {
            logger.warn("MinIO检查文件存在性时出错: {}", objectKey, e);
            return false;
        }
    }
    
    @Override
    public long getFileSize(String objectKey) throws IOException {
        try {
            StatObjectResponse stat = minioClient.statObject(
                StatObjectArgs.builder()
                    .bucket(minIOProperties.getBucketName())
                    .object(objectKey)
                    .build()
            );
            return stat.size();
            
        } catch (Exception e) {
            logger.error("MinIO获取文件大小失败: {}", objectKey, e);
            throw new IOException("MinIO获取文件大小失败: " + e.getMessage(), e);
        }
    }
    
    @Override
    public String copyFile(String sourceObjectKey, String targetFileName) throws IOException {
        logger.debug("MinIO复制文件: {} -> {}", sourceObjectKey, targetFileName);
        
        try {
            // 生成目标对象键
            String targetObjectKey = generateObjectKey(targetFileName);
            
            // 复制对象
            minioClient.copyObject(
                CopyObjectArgs.builder()
                    .bucket(minIOProperties.getBucketName())
                    .object(targetObjectKey)
                    .source(
                        CopySource.builder()
                            .bucket(minIOProperties.getBucketName())
                            .object(sourceObjectKey)
                            .build()
                    )
                    .build()
            );
            
            logger.info("MinIO文件复制成功: {} -> {}", sourceObjectKey, targetObjectKey);
            return targetObjectKey;
            
        } catch (Exception e) {
            logger.error("MinIO文件复制失败: {}", sourceObjectKey, e);
            throw new IOException("MinIO文件复制失败: " + e.getMessage(), e);
        }
    }
    
    @Override
    public String copyFile(String sourceObjectKey) throws IOException {
        // 从源对象键中提取文件扩展名
        String extension = getFileExtension(sourceObjectKey);
        String targetFileName = "copy_" + UUID.randomUUID().toString() + extension;
        return copyFile(sourceObjectKey, targetFileName);
    }
    
    @Override
    public String moveFile(String sourceObjectKey, String targetFileName) throws IOException {
        // MinIO 不支持直接移动，使用复制+删除的方式
        String targetObjectKey = copyFile(sourceObjectKey, targetFileName);
        deleteFile(sourceObjectKey);
        return targetObjectKey;
    }

    @Override
    public String generateDownloadUrl(String objectKey, Duration expiration) {
        logger.debug("MinIO生成下载URL: {}, 有效期: {}秒", objectKey, expiration.getSeconds());

        // 处理历史数据中可能包含的 rickpan/ 前缀
        String cleanObjectKey = objectKey;
        if (objectKey.startsWith("rickpan/")) {
            cleanObjectKey = objectKey.substring("rickpan/".length());
            logger.debug("移除历史前缀: {} -> {}", objectKey, cleanObjectKey);
        }

        return urlGenerationService.generateDownloadUrl(cleanObjectKey, expiration);
    }

    @Override
    public String generateDownloadUrl(String objectKey, Duration expiration, boolean forceDownload, String filename) {
        logger.debug("MinIO生成下载URL: {}, 有效期: {}秒, forceDownload: {}, filename: {}",
                    objectKey, expiration.getSeconds(), forceDownload, filename);

        // 处理历史数据中可能包含的 rickpan/ 前缀
        String cleanObjectKey = objectKey;
        if (objectKey.startsWith("rickpan/")) {
            cleanObjectKey = objectKey.substring("rickpan/".length());
            logger.debug("移除历史前缀: {} -> {}", objectKey, cleanObjectKey);
        }

        return urlGenerationService.generateDownloadUrl(cleanObjectKey, expiration, forceDownload, filename);
    }

    @Override
    public String generatePreviewUrl(String objectKey, Duration expiration) {
        logger.debug("MinIO生成预览URL: {}, 有效期: {}秒", objectKey, expiration.getSeconds());

        // 处理历史数据中可能包含的 rickpan/ 前缀
        String cleanObjectKey = objectKey;
        if (objectKey.startsWith("rickpan/")) {
            cleanObjectKey = objectKey.substring("rickpan/".length());
            logger.debug("移除历史前缀: {} -> {}", objectKey, cleanObjectKey);
        }

        return urlGenerationService.generatePreviewUrl(cleanObjectKey, expiration);
    }

    @Override
    public java.util.Map<String, String> batchGenerateDownloadUrls(java.util.List<String> objectKeys, Duration expiration) {
        logger.debug("MinIO批量生成下载URL: {} 个文件", objectKeys.size());
        return urlGenerationService.batchGenerateDownloadUrls(objectKeys, expiration);
    }

    @Override
    public java.util.Map<String, String> batchGeneratePreviewUrls(java.util.List<String> objectKeys, Duration expiration) {
        logger.debug("MinIO批量生成预览URL: {} 个文件", objectKeys.size());
        return urlGenerationService.batchGeneratePreviewUrls(objectKeys, expiration);
    }

    @Override
    public String calculateFileSHA256(String objectKey) throws IOException {
        try {
            // 获取对象输入流
            try (InputStream inputStream = minioClient.getObject(
                GetObjectArgs.builder()
                    .bucket(minIOProperties.getBucketName())
                    .object(objectKey)
                    .build()
            )) {
                // 计算SHA256
                MessageDigest digest = MessageDigest.getInstance("SHA-256");
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    digest.update(buffer, 0, bytesRead);
                }

                byte[] hashBytes = digest.digest();
                StringBuilder sb = new StringBuilder();
                for (byte b : hashBytes) {
                    sb.append(String.format("%02x", b));
                }

                String hash = sb.toString();
                logger.debug("MinIO计算文件SHA256: {} -> {}", objectKey, hash);
                return hash;
            }

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256算法不可用", e);
        } catch (Exception e) {
            logger.error("MinIO计算文件SHA256失败: {}", objectKey, e);
            throw new IOException("MinIO计算文件SHA256失败: " + e.getMessage(), e);
        }
    }

    @Override
    public String calculateChunkMD5(MultipartFile chunk) throws IOException {
        try (InputStream inputStream = chunk.getInputStream()) {
            return DigestUtils.md5DigestAsHex(inputStream);
        }
    }

    @Override
    public String getStorageType() {
        return "minio";
    }

    @Override
    public void initStorage() {
        try {
            // logger.debug("初始化MinIO存储，检查存储桶: {}", minIOProperties.getBucketName()); // 开发调试用
            ensureBucketExists();

            // 测试连接
            testConnection();

            // logger.debug("MinIO存储初始化完成"); // 开发调试用

        } catch (Exception e) {
            logger.error("MinIO存储初始化失败", e);
            throw new RuntimeException("MinIO存储初始化失败: " + e.getMessage(), e);
        }
    }

    @Override
    public String getFileExtension(String fileName) {
        if (fileName == null || !fileName.contains(".")) {
            return "";
        }
        return fileName.substring(fileName.lastIndexOf("."));
    }

    @Override
    public void cleanupChunks(String uploadId) {
        logger.debug("MinIO清理分片上传会话: {}", uploadId);

        try {
            // 中止多部分上传（如果存在）
            multipartUploadManager.abortMultipartUpload(uploadId);
            logger.debug("MinIO分片上传会话清理完成: {}", uploadId);

        } catch (Exception e) {
            logger.warn("MinIO清理分片上传会话失败: {}", uploadId, e);
        }
    }

    /**
     * 确保存储桶存在
     */
    private void ensureBucketExists() throws Exception {
        String bucketName = minIOProperties.getBucketName();

        boolean exists = minioClient.bucketExists(
            BucketExistsArgs.builder()
                .bucket(bucketName)
                .build()
        );

        if (!exists) {
            logger.info("创建MinIO存储桶: {}", bucketName);
            minioClient.makeBucket(
                MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build()
            );
            logger.info("MinIO存储桶创建成功: {}", bucketName);
        }
    }

    /**
     * 生成对象键（保持与现有路径格式兼容）
     * 格式: yyyy/MM/dd/uuid.ext（与本地存储保持一致）
     */
    private String generateObjectKey(String originalFileName) {
        LocalDate now = LocalDate.now();
        String datePath = now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        String extension = getFileExtension(originalFileName);
        String uuid = UUID.randomUUID().toString();

        return datePath + "/" + uuid + extension;
    }

    /**
     * 测试MinIO连接
     */
    private void testConnection() throws Exception {
        try {
            // 尝试列出存储桶来测试连接
            minioClient.listBuckets();
            logger.debug("MinIO连接测试成功");

        } catch (Exception e) {
            logger.error("MinIO连接测试失败", e);
            throw new Exception("MinIO连接测试失败: " + e.getMessage(), e);
        }
    }
}
